import Base.Dates: Nanosecond, Microsecond, Millisecond, Second,
                   Minute, Hour, Day, Week, Month, Year,
                   nanosecond, microsecond, millisecond, second,
                   minute, hour, day, week, month, year

# TS:=TypeSingular FS:=FunctionSingular TP:=TypePlural FP:=FunctionPlural 
# SF:= SingularFactor, PF:=Plural
for (TS,FS,TP,FP,SF,PF) in (
    (:Nanosecond, :nanosecond, :Nanoseconds, :nanoseconds, NANOSECONDS_PER_MICROSECOND, NANOSECONDS_PER_DAY), 
    (:Microsecond, :microsecond, :Microseconds, :microseconds, MICROSECONDS_PER_MILLISECOND, MICROSECONDS_PER_DAY), 
    (:Millisecond, :millisecond,:Milliseconds, :milliseconds, MILLISECONDS_PER_SECOND, MILLISECONDS_PER_DAY),
    (:Second, :second, :Seconds, :seconds, SECONDS_PER_MINUTE, SECONDS_PER_DAY), 
    (:Minute, :minute, :Minutes, :minutes, MINUTES_PER_HOUR, MINUTES_PER_DAY), 
    (:Hour, :hour, :Hours, :hours, HOURS_PER_DAY, HOURS_PER_DAY),
    (:Day, :day, :Days, :days, DAYS_PER_DAY, DAYS_PER_DAY),
    (:Week, :week, :Weeks, :weeks, WEEKS_PER_WEEK, WEEKS_PER_WEEK)
  )
    # e.g. nanoseconds(x::Second) = nanoseconds(Seconds(x))
    for F in (:nanoseconds, :microseconds, :milliseconds, :seconds, :minutes, :hours, :days, :weeks)
        @eval $F != $FP ? $F(x::$TS) = $F($TP(x)) : nothing
    end    
  @eval begin
    # e.g. Second(x::Second) = x
    $TS(x::$TS) = x
    # e.g. second(x::Second) = value(x)
    $FS(x::$TS) = value(x)
    # e.g. seconds(x:Second) = value(x) 
    $FP(x::$TS) = value(x)
    # e.g. second(x::Seconds) = rem(value(x), SECONDS_PER_MINUTE)
    $FS(x::$TP) = rem(value(x), $SF)
    # e.g. seconds(x:Seconds) = value(x) 
    # $FP(x::$TP) = value(x)
    # e.g. Second(x::Seconds) = Second(second(x))
    $TS(x::$TP) = $TS($FS(x))
    # e.g. Seconds(x::Second) = Seconds(seconds(x))
    $TP(x::$TS) = $TP($FP(x))
  end   
end

nanosecond(x::T) where T<:NanosecondBasedGrain = mod(nanoseconds(x), NANOSECONDS_PER_MICROSECOND)
microsecond(x::T) where T<:NanosecondBasedGrain = mod(microseconds(x), MICROSECONDS_PER_MILLISECOND)
millisecond(x::T) where T<:NanosecondBasedGrain = mod(milliseconds(x), MILLISECONDS_PER_SECOND)
second(x::T) where T<:NanosecondBasedGrain = mod(seconds(x), SECONDS_PER_MINUTE)
minute(x::T) where T<:NanosecondBasedGrain = mod(minutes(x), MINUTES_PER_HOUR)
hour(x::T) where T<:NanosecondBasedGrain = mod(hours(x), HOURS_PER_DAY)
day(x::T) where T<:NanosecondBasedGrain = days(x)
week(x::T) where T<:NanosecondBasedGrain = weeks(x)

for T in (:Nanosecond, :Microsecond, :Millisecond, :Second,:Minute, :Hour, :Day, :Week)
  @eval begin
    quarterhours(x::$T) = div(minutes(x), MINUTES_PER_QUARTERHOUR)
    QuarterHours(x::$T) = QuarterHours( quarterhours(x) )
    $T(x::G) where G<:NanosecondBasedGrain = $T(Nanoseconds(x))
  end
end  
for T in (:Nanoseconds, :Microseconds, :Milliseconds, :Seconds, :Minutes, :Hours, :Days, :Weeks)
    @eval $T(x::G) where G<:Base.Dates.Period = $T(Nanoseconds(x))
end

# TS:=TypeSingular FS:=FunctionSingular TP:=TypePlural FP:=FunctionPlural 
# SF:= SingularFactor, PF:=Plural
for (TS,FS,TP,FP,SF,PF) in (
    (:Month, :month, :Months, :months, MONTHS_PER_YEAR, MONTHS_PER_MONTH), 
    (:Year, :year, :Years, :years, YEARS_PER_YEAR, YEARS_PER_YEAR)
  )
    # e.g. months(x::Year) = months(Years(x))
    for F in (:months, :years)
        @eval $F != $FP ? $F(x::$TS) = $F($TP(x)) : nothing
    end    
  @eval begin
    # e.g. Month(x::Month) = x
    $TS(x::$TS) = x
    # e.g. month(x::Month) = value(x)
    $FS(x::$TS) = value(x)
    # e.g. months(x:Month) = value(x) 
    $FP(x::$TS) = value(x)
    # e.g. month(x::Months) = rem(value(x), MONTHS_PER_YEAR)
    $FS(x::$TP) = rem(value(x), $SF)
    # e.g. months(x:Months) = value(x) 
    # $FP(x::$TP) = value(x)
    # e.g. Month(x::Months) = Month(month(x))
    $TS(x::$TP) = $TS($FS(x))
    # e.g. Months(x::Month) = Months(month(x))
    $TP(x::$TS) = $TP($FP(x))
  end   
end

month(x::T) where T<:MonthBasedGrain = mod(months(x), MONTHS_PER_YEAR)
year(x::T) where T<:MonthBasedGrain = years(x)

for T in (:Month, :Year)
  @eval begin
    quarters(x::$T) = div(months(x), MONTHS_PER_QUARTER)
    Quarters(x::$T) = Quarters( quarters(x) )
    decades(x::$T) = div(years(x), YEARS_PER_DECADE)
    Decades(x::$T) = Decades( decades(x) )
    centuries(x::$T) = div(years(x), YEARS_PER_CENTURY)
    Centuries(x::$T) = Centuries( centuries(x) )
    $T(x::G) where G<:NanosecondBasedGrain = $T(Months(x))
  end
end  

Months(x::P) where P<:Union{Month, Year} = Months(months(x))
Months(x::P) where P<:Base.Dates.Period = zero(Int4Span)
Years(x::P) where P<:Union{Month, Year} = Years(Months(x))
Years(x::P) where P<:Base.Dates.Period = zero(Int4Span)
